Skip to content

[pull] main from TryGhost:main#1091

Merged
pull[bot] merged 11 commits intocode:mainfrom
TryGhost:main
Apr 23, 2026
Merged

[pull] main from TryGhost:main#1091
pull[bot] merged 11 commits intocode:mainfrom
TryGhost:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented Apr 23, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

minimaluminium and others added 11 commits April 23, 2026 18:15
no issues

Follow-up to #27436 with a few design tweaks to the new gift reminder
email. Also aligns the CTA with the in-progress "Continue subscription"
button on the Portal account page.

- Rewrote the body copy and renamed the CTA *"Manage subscription"* →
*"Continue subscription"* to match the in-progress Portal account button
- Removed the conditional *"Hi {name},"* greeting so the h1 leads
directly
- Replaced the cadence row with a *"Price after gift ends"* row, and
wired tier currency + monthly/yearly price through the reminder flow
ref https://linear.app/ghost/issue/MIG-1354/

- Adds a beehiiv button to the migration tools
- Add beehiiv as a search term in Settings
…aining days (#27432)

ref https://linear.app/ghost/issue/BER-3477

Allows gift members to continue as a paid subscriber without losing the
remainder of their gift: any remaining gift days are converted to free
trial days. As the trial mechanism only works if the member continues on
the same tier (can't convert gift days on tier A to trial days on tier
B), the UI for gift members now only shows an option to continue on the
same tier.
ref https://linear.app/ghost/issue/ONC-1677

The publish_packages job held `id-token: write` while also running the
PR-controlled `nx build` step on pull requests, which meant arbitrary PR
code could execute in a workflow context capable of minting an npm OIDC
token for @tryghost/* packages. Split into a build_packages job that
runs on PRs with no id-token permission, and a publish_packages job
gated to push-to-main that retains id-token: write for trusted
publishing only.
no ref

Split out from the other leaf utility bumps because the upstream rewrite swaps the `lodash.template` backend for a native `String#replace`, which is a meaningfully different execution path.
ref
https://linear.app/ghost/issue/NY-1204/clean-up-post-title-alignment-and-title-color-attributes

This should have no user-facing impact.

These fields snuck into the original implementation, but ended up not
being used for welcome emails. These are newsletter-only fields. Since
the email-design component currently is only used by welcome emails,
this cleans up the current types to match the current UI.
…exiting settings (#27516)

ref
https://linear.app/ghost/issue/NY-1234/flaky-member-welcome-emails-e2e-test

## Why this change?

Pressing `Escape` inside the welcome emails "Customize" flow could exit
settings instead of just dismissing the active UI layer. That made the
interaction flaky in E2E and, more importantly, let a nested control
bypass the expected unsaved-changes flow.

## Reproduction
1. Go to Settings > Welcome emails
2. Open the "Customize" modal
3. Go to the Design tab
4. In rapid succession: click on one of the color swatches and
immediately hit Escape

## Expected behavior
Escape should close the color picker, but leave the Customize modal open

## Actual behavior
If you time it just right, the Customize modal will close, then Settings
will close, and you'll land on the Analytics screen (or where ever you
were before opening settings to begin with).

## Root cause

`admin-x-settings` has a page-level `Escape` handler that navigates away
from settings when no modal is open.

The welcome email customize UI is built with Shade/Radix dialogs and
popovers, but the page-level guard was still only checking for the
legacy `#modal-backdrop` element. That created two failure modes:

1. The customize modal and dirty-confirm dialog were not always
recognized as "an open modal", so the page-level handler could still
react to `Escape`.
6. The color picker popover mounts through a portal. Right after opening
it, there is a brief window where the popover has been requested but its
content has not mounted yet, so its `onEscapeKeyDown` handler cannot
intercept the keypress. In that gap, `Escape` can bubble to the
page-level handler and exit settings.

## Why this fix is correct

This fix closes the gap at the right layers instead of weakening the
global shortcut.

- `MainContent` now treats open Radix `dialog` and `alertdialog`
elements as active modals, not just the legacy backdrop. That keeps the
page-level `Escape` shortcut disabled while the customize modal or
unsaved-changes confirmation is open.
- `ColorPickerField` attaches an early capture-phase `Escape` listener
as soon as the popover is opened. That means the first `Escape` is
claimed by the color picker even if the popover content has not mounted
yet. Once the popover is mounted, the normal popover escape handling
still applies, and the temporary listener is removed when the popover
closes or the component unmounts.

Together, those changes preserve the intended dismissal order:

1. First `Escape`: close the nested color picker.
2. Next `Escape`: show or dismiss the customize modal's unsaved-changes
confirmation (if dirty).
7. Only when no modal is open should the settings page itself handle
`Escape`.

## Test coverage

The PR adds:

- unit coverage for both the mounted-popover case and the
immediate-after-open race
- e2e coverage proving `Escape` closes the color picker first and still
respects the unsaved-changes confirmation flow
no ref

Moves three `@tryghost/*` packages to `pnpm-workspace.yaml`'s `catalog:` entry so the version is declared in one place instead of being mirrored across up to seven workspaces and `pnpm.overrides`.
ref https://linear.app/ghost/issue/NY-1191
ref #27437

This creates a new permission, "Poll automations", which the Scheduler
Integration role gets.

This will be used in [an upcoming change][0]. It's a separate patch
because it contains a database migration.

[0]: #27437

Co-authored-by: Troy Ciesco <tmciesco@gmail.com>
closes https://linear.app/ghost/issue/NY-1233
ref #27485

Welcome emails use `<figure>` and `<figcaption>` for images and their
captions, which some email clients don't support. That causes the
styling to be messed up in those clients.

[Newsletter rendering had already fixed this.][0] This patch fixes it
the same way.

In the long term, we should unify these renderers. In the short term,
let's fix this bug.

[0]: https://github.com/TryGhost/Ghost/blob/d26bfdb0b20f029a82709782804164d621848d3f/ghost/core/core/server/services/email-service/email-renderer.js#L514-L544
Reverts #27533.

The catalog resolutions do not work because `catalog:` does not work
outside of a workspace, so `npm i` is broken for these packages. This
largely should not matter, because anyone working on these should be
doing so by cloning the TryGhost/Ghost repo, which is a workspace, and
the built assets are also unaffected.

I will follow up with more holistic changes to adopt the pnpm catalog
after dependencies are brought more up to date.
@pull pull Bot locked and limited conversation to collaborators Apr 23, 2026
@pull pull Bot added the ⤵️ pull label Apr 23, 2026
@pull pull Bot merged commit 4f6d484 into code:main Apr 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants